<!-- build time:Tue Feb 18 2020 17:19:27 GMT+0800 (China Standard Time) --><!DOCTYPE html><html lang="zh"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no,minimal-ui"><meta name="renderer" content="webkit"><meta http-equiv="Cache-Control" content="no-transform"><meta http-equiv="Cache-Control" content="no-siteapp"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black"><meta name="format-detection" content="telephone=no,email=no,adress=no"><meta name="theme-color" content="#000000"><meta http-equiv="window-target" content="_top"><title>Spring Boot 缓存 Cache 入门 | calm&#39;s blog</title><meta name="description" content="本文主要参考：芋道 Spring Boot 缓存 Cache 入门1. 概述在这里，我们暂时不考虑优化数据库的硬件，索引等等手段。读写分离。通过将读操作分流到从节点，避免主节点压力过多。分库分表。通过将读写操作分摊到多个节点，避免单节点压力过多。缓存。相比数据库来说，缓存往往能够提供更快的读性能，减小数据库的压力。关于缓存和数据库的性能情况，可以看看如下两篇文章：《性能测试 —— Redis 基准"><meta property="og:type" content="article"><meta property="og:title" content="Spring Boot 缓存 Cache 入门"><meta property="og:url" content="https://calm_java.gitee.io/blog/2020/01/09/labs-springboot2-cache/index.html"><meta property="og:site_name" content="卡姆"><meta property="og:description" content="本文主要参考：芋道 Spring Boot 缓存 Cache 入门1. 概述在这里，我们暂时不考虑优化数据库的硬件，索引等等手段。读写分离。通过将读操作分流到从节点，避免主节点压力过多。分库分表。通过将读写操作分摊到多个节点，避免单节点压力过多。缓存。相比数据库来说，缓存往往能够提供更快的读性能，减小数据库的压力。关于缓存和数据库的性能情况，可以看看如下两篇文章：《性能测试 —— Redis 基准"><meta property="og:locale" content="zh_CN"><meta property="og:image" content="https://calm_java.gitee.io/blog/2020/01/09/labs-springboot2-cache/1.png"><meta property="og:image" content="https://calm_java.gitee.io/blog/2020/01/09/labs-springboot2-cache/2.png"><meta property="og:image" content="https://calm_java.gitee.io/blog/2020/01/09/labs-springboot2-cache/3.png"><meta property="og:image" content="https://calm_java.gitee.io/blog/2020/01/09/labs-springboot2-cache/4.png"><meta property="article:published_time" content="2020-01-09T03:23:51.000Z"><meta property="article:modified_time" content="2020-01-09T05:29:44.311Z"><meta property="article:author" content="calm"><meta property="article:tag" content="缓存"><meta name="twitter:card" content="summary"><meta name="twitter:image" content="https://calm_java.gitee.io/blog/2020/01/09/labs-springboot2-cache/1.png"><link rel="canonical" href="https://calm_java.gitee.io/blog/2020/01/09/labs-springboot2-cache/index.html"><link rel="alternate" href="/atom.xml" title="卡姆" type="application/atom+xml"><link rel="icon" href="/blog/images/avatar.jpg" type="image/x-icon"><link rel="stylesheet" href="/blog/css/style.css"><link rel="stylesheet" href="//cdn.jsdelivr.net/npm/gitalk@1.4.0/dist/gitalk.min.css"><meta name="generator" content="Hexo 4.2.0"></head><body class="main-center theme-green" itemscope itemtype="http://schema.org/WebPage"><header class="header" itemscope itemtype="http://schema.org/WPHeader"><div class="slimContent"><div class="navbar-header"><div class="profile-block text-center"><a id="avatar" href="https://github.com/yuyong725" target="_blank"><img class="img-circle img-rotate" src="/blog/images/avatar.jpg" width="200" height="200"></a><h2 id="name" class="hidden-xs hidden-sm">卡姆</h2><h3 id="title" class="hidden-xs hidden-sm hidden-md">a Java coder need calm</h3><small id="location" class="text-muted hidden-xs hidden-sm"><i class="icon icon-map-marker"></i> hangzhou, China</small></div><div class="search" id="search-form-wrap"><form class="search-form sidebar-form"><div class="input-group"><input type="text" class="search-form-input form-control" placeholder="搜索"> <span class="input-group-btn"><button type="submit" class="search-form-submit btn btn-flat" onclick="return!1"><i class="icon icon-search"></i></button></span></div></form><div class="ins-search"><div class="ins-search-mask"></div><div class="ins-search-container"><div class="ins-input-wrapper"><input type="text" class="ins-search-input" placeholder="想要查找什么..." x-webkit-speech> <button type="button" class="close ins-close ins-selectable" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button></div><div class="ins-section-wrapper"><div class="ins-section-container"></div></div></div></div></div><button class="navbar-toggle collapsed" type="button" data-toggle="collapse" data-target="#main-navbar" aria-controls="main-navbar" aria-expanded="false"><span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span></button></div><nav id="main-navbar" class="collapse navbar-collapse" itemscope itemtype="http://schema.org/SiteNavigationElement" role="navigation"><ul class="nav navbar-nav main-nav"><li class="menu-item menu-item-home"><a href="/blog/."><i class="icon icon-home-fill"></i> <span class="menu-title">首页</span></a></li><li class="menu-item menu-item-archives"><a href="/blog/archives"><i class="icon icon-archives-fill"></i> <span class="menu-title">归档</span></a></li><li class="menu-item menu-item-categories"><a href="/blog/categories"><i class="icon icon-folder"></i> <span class="menu-title">分类</span></a></li><li class="menu-item menu-item-tags"><a href="/blog/tags"><i class="icon icon-tags"></i> <span class="menu-title">标签</span></a></li><li class="menu-item menu-item-repository"><a href="/blog/repository"><i class="icon icon-project"></i> <span class="menu-title">项目</span></a></li><li class="menu-item menu-item-books"><a href="/blog/books"><i class="icon icon-book-fill"></i> <span class="menu-title">书单</span></a></li><li class="menu-item menu-item-links"><a href="/blog/links"><i class="icon icon-friendship"></i> <span class="menu-title">友链</span></a></li><li class="menu-item menu-item-about"><a href="/blog/about"><i class="icon icon-cup-fill"></i> <span class="menu-title">关于</span></a></li></ul><ul class="social-links"><li><a href="https://github.com/yuyong725" target="_blank" title="Github" data-toggle="tooltip" data-placement="top"><i class="icon icon-github"></i></a></li></ul></nav></div></header><aside class="sidebar" itemscope itemtype="http://schema.org/WPSideBar"><div class="slimContent"><div class="widget"><h3 class="widget-title">公告</h3><div class="widget-body"><div id="board"><div class="content"><p>欢迎交流与分享经验!</p></div></div></div></div><div class="widget"><h3 class="widget-title">分类</h3><div class="widget-body"><ul class="category-list"><li class="category-list-item"><a class="category-list-link" href="/blog/categories/CI-DI/">CI/DI</a><span class="category-list-count">1</span></li><li class="category-list-item"><a class="category-list-link" href="/blog/categories/git/">git</a><span class="category-list-count">1</span></li><li class="category-list-item"><a class="category-list-link" href="/blog/categories/labs/">labs</a><span class="category-list-count">14</span></li></ul></div></div><div class="widget"><h3 class="widget-title">标签</h3><div class="widget-body"><ul class="tag-list" itemprop="keywords"><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/MyBatis/" rel="tag">MyBatis</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/Oauth2/" rel="tag">Oauth2</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/Spring-Security/" rel="tag">Spring Security</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/SpringMVC/" rel="tag">SpringMVC</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/docker/" rel="tag">docker</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/dubbo/" rel="tag">dubbo</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/git/" rel="tag">git</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/jdbctemplate/" rel="tag">jdbctemplate</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/jerkins/" rel="tag">jerkins</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/job/" rel="tag">job</a><span class="tag-list-count">2</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/jpa/" rel="tag">jpa</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/pdf/" rel="tag">pdf</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/redis/" rel="tag">redis</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8/" rel="tag">分库分表</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E5%A4%9A%E6%95%B0%E6%8D%AE%E6%BA%90/" rel="tag">多数据源</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E7%89%88%E6%9C%AC%E7%AE%A1%E7%90%86/" rel="tag">数据库版本管理</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E8%BF%9E%E6%8E%A5%E6%B1%A0/" rel="tag">数据库连接池</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E7%BC%93%E5%AD%98/" rel="tag">缓存</a><span class="tag-list-count">2</span></li></ul></div></div><div class="widget"><h3 class="widget-title">标签云</h3><div class="widget-body tagcloud"><a href="/blog/tags/MyBatis/" style="font-size:13px">MyBatis</a> <a href="/blog/tags/Oauth2/" style="font-size:13px">Oauth2</a> <a href="/blog/tags/Spring-Security/" style="font-size:13px">Spring Security</a> <a href="/blog/tags/SpringMVC/" style="font-size:13px">SpringMVC</a> <a href="/blog/tags/docker/" style="font-size:13px">docker</a> <a href="/blog/tags/dubbo/" style="font-size:13px">dubbo</a> <a href="/blog/tags/git/" style="font-size:13px">git</a> <a href="/blog/tags/jdbctemplate/" style="font-size:13px">jdbctemplate</a> <a href="/blog/tags/jerkins/" style="font-size:13px">jerkins</a> <a href="/blog/tags/job/" style="font-size:14px">job</a> <a href="/blog/tags/jpa/" style="font-size:13px">jpa</a> <a href="/blog/tags/pdf/" style="font-size:13px">pdf</a> <a href="/blog/tags/redis/" style="font-size:13px">redis</a> <a href="/blog/tags/%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8/" style="font-size:13px">分库分表</a> <a href="/blog/tags/%E5%A4%9A%E6%95%B0%E6%8D%AE%E6%BA%90/" style="font-size:13px">多数据源</a> <a href="/blog/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E7%89%88%E6%9C%AC%E7%AE%A1%E7%90%86/" style="font-size:13px">数据库版本管理</a> <a href="/blog/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E8%BF%9E%E6%8E%A5%E6%B1%A0/" style="font-size:13px">数据库连接池</a> <a href="/blog/tags/%E7%BC%93%E5%AD%98/" style="font-size:14px">缓存</a></div></div><div class="widget"><h3 class="widget-title">归档</h3><div class="widget-body"><ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/blog/archives/2020/02/">二月 2020</a><span class="archive-list-count">5</span></li><li class="archive-list-item"><a class="archive-list-link" href="/blog/archives/2020/01/">一月 2020</a><span class="archive-list-count">8</span></li><li class="archive-list-item"><a class="archive-list-link" href="/blog/archives/2019/12/">十二月 2019</a><span class="archive-list-count">4</span></li></ul></div></div><div class="widget"><h3 class="widget-title">最新文章</h3><div class="widget-body"><ul class="recent-post-list list-unstyled no-thumbnail"><li><div class="item-inner"><p class="item-category"><a class="category-link" href="/blog/categories/labs/">labs</a></p><p class="item-title"><a href="/blog/2020/02/18/labs-springboot2-async-Job/" class="title">Spring Boot 异步任务入门</a></p><p class="item-date"><time datetime="2020-02-18T05:10:39.000Z" itemprop="datePublished">2020-02-18</time></p></div></li><li><div class="item-inner"><p class="item-category"><a class="category-link" href="/blog/categories/labs/">labs</a></p><p class="item-title"><a href="/blog/2020/02/18/labs-springboot2-job/" class="title">Spring Boot 定时任务入门</a></p><p class="item-date"><time datetime="2020-02-18T02:26:31.000Z" itemprop="datePublished">2020-02-18</time></p></div></li><li><div class="item-inner"><p class="item-category"><a class="category-link" href="/blog/categories/labs/">labs</a></p><p class="item-title"><a href="/blog/2020/02/17/labs-springboot2-database-version-control/" class="title">Spring Boot 数据库版本管理入门</a></p><p class="item-date"><time datetime="2020-02-17T12:05:54.000Z" itemprop="datePublished">2020-02-17</time></p></div></li><li><div class="item-inner"><p class="item-category"><a class="category-link" href="/blog/categories/labs/">labs</a></p><p class="item-title"><a href="/blog/2020/02/17/labs-springboot2-sharding-datasource/" class="title">Spring Boot 分库分表入门</a></p><p class="item-date"><time datetime="2020-02-17T08:15:31.000Z" itemprop="datePublished">2020-02-17</time></p></div></li><li><div class="item-inner"><p class="item-category"><a class="category-link" href="/blog/categories/labs/">labs</a></p><p class="item-title"><a href="/blog/2020/02/13/labs-springboot2-dynamic-datasource/" class="title">Spring Boot 多数据源（读写分离）入门</a></p><p class="item-date"><time datetime="2020-02-13T03:18:37.000Z" itemprop="datePublished">2020-02-13</time></p></div></li></ul></div></div></div></aside><aside class="sidebar sidebar-toc collapse in" id="collapseToc" itemscope itemtype="http://schema.org/WPSideBar"><div class="slimContent"><nav id="toc" class="article-toc"><h3 class="toc-title">文章目录</h3><ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#1-概述"><span class="toc-text">1. 概述</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#2-注解"><span class="toc-text">2. 注解</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#2-1-Cacheable"><span class="toc-text">2.1 @Cacheable</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-2-CachePut"><span class="toc-text">2.2 @CachePut</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-3-CacheEvict"><span class="toc-text">2.3 @CacheEvict</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-4-Caching"><span class="toc-text">2.4 @Caching</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-5-CacheConfig"><span class="toc-text">2.5 @CacheConfig</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-6-EnableCaching"><span class="toc-text">2.6 @EnableCaching</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#3-Spring-Boot-集成"><span class="toc-text">3. Spring Boot 集成</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#4-Ehcache-示例"><span class="toc-text">4. Ehcache 示例</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#4-1-引入依赖"><span class="toc-text">4.1 引入依赖</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#4-2-应用配置文件"><span class="toc-text">4.2 应用配置文件</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#4-3-Ehcache-配置文件"><span class="toc-text">4.3 Ehcache 配置文件</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#4-4-EhcacheApplication"><span class="toc-text">4.4 EhcacheApplication</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#4-5-UserDO"><span class="toc-text">4.5 UserDO</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#4-6-UserMapper"><span class="toc-text">4.6 UserMapper</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#4-7-UserMapperTest"><span class="toc-text">4.7 UserMapperTest</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#5-Redis-示例"><span class="toc-text">5. Redis 示例</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#5-1-引入依赖"><span class="toc-text">5.1 引入依赖</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#5-2-应用配置文件"><span class="toc-text">5.2 应用配置文件</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#5-3-RedisApplication"><span class="toc-text">5.3 RedisApplication</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#5-4-UserDO"><span class="toc-text">5.4 UserDO</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#5-5-UserMapper"><span class="toc-text">5.5 UserMapper</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#5-6-UserMapperTest"><span class="toc-text">5.6 UserMapperTest</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#5-7-过期时间"><span class="toc-text">5.7 过期时间</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#666-彩蛋"><span class="toc-text">666. 彩蛋</span></a></li></ol></nav></div></aside><main class="main" role="main"><div class="content"><article id="post-labs-springboot2-cache" class="article article-type-post" itemscope itemtype="http://schema.org/BlogPosting"><div class="article-header"><h1 class="article-title" itemprop="name">Spring Boot 缓存 Cache 入门</h1><div class="article-meta"><span class="article-date"><i class="icon icon-calendar-check"></i> <a href="/blog/2020/01/09/labs-springboot2-cache/" class="article-date"><time datetime="2020-01-09T03:23:51.000Z" itemprop="datePublished">2020-01-09</time></a></span> <span class="article-category"><i class="icon icon-folder"></i> <a class="article-category-link" href="/blog/categories/labs/">labs</a></span> <span class="article-tag"><i class="icon icon-tags"></i> <a class="article-tag-link" href="/blog/tags/%E7%BC%93%E5%AD%98/" rel="tag">缓存</a></span> <span class="article-read hidden-xs"><i class="icon icon-eye-fill" aria-hidden="true"></i> <span id="busuanzi_container_page_pv"><span id="busuanzi_value_page_pv">0</span></span></span> <span class="post-comment"><i class="icon icon-comment"></i> <a href="/blog/2020/01/09/labs-springboot2-cache/#comments" class="article-comment-link">评论</a></span> <span class="post-wordcount hidden-xs" itemprop="wordCount">字数统计: 7.1k(字)</span> <span class="post-readcount hidden-xs" itemprop="timeRequired">阅读时长: 30(分)</span></div></div><div class="article-entry marked-body" itemprop="articleBody"><blockquote><p>本文主要参考：<a href="http://www.iocoder.cn/Spring-Boot/Cache/" target="_blank" rel="noopener">芋道 Spring Boot 缓存 Cache 入门</a></p></blockquote><h1 id="1-概述"><a href="#1-概述" class="headerlink" title="1. 概述"></a>1. 概述</h1><blockquote><p>在这里，我们暂时不考虑优化数据库的硬件，索引等等手段。</p></blockquote><ul><li>读写分离。通过将读操作分流到从节点，避免主节点压力过多。</li><li>分库分表。通过将读写操作分摊到多个节点，避免单节点压力过多。</li><li>缓存。相比数据库来说，缓存往往能够提供更快的读性能，减小数据库的压力。关于缓存和数据库的性能情况，可以看看如下两篇文章：<ul><li><a href="http://www.iocoder.cn/Performance-Testing/Redis-benchmark/?self" target="_blank" rel="noopener">《性能测试 —— Redis 基准测试》</a></li><li><a href="http://www.iocoder.cn/Performance-Testing/MySQL-benchmark/?self" target="_blank" rel="noopener">《性能测试 —— MySQL 基准测试》</a></li></ul></li></ul><p>那么，在引入缓存之后，我们的读操作的代码，往往代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> UserMapper userMapper; <span class="comment">// 读取 DB</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> UserCacheDao userCacheDao; <span class="comment">// 读取 Cache</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> UserDO <span class="title">getUser</span><span class="params">(Integer id)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 从 Cache 中，查询用户信息</span></span><br><span class="line">    UserDO user = userCacheDao.get(id);</span><br><span class="line">    <span class="keyword">if</span> (user != <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> user;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 如果 Cache 查询不到，从 DB 中读取</span></span><br><span class="line">    user = userMapper.selectById(id);</span><br><span class="line">    <span class="keyword">if</span> (user != <span class="keyword">null</span>) &#123; <span class="comment">// 非空，则缓存到 Cache 中</span></span><br><span class="line">        userCacheDao.put(user);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 返回结果</span></span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>这段代码，是比较常用的缓存策略，俗称“被动写”。整体步骤如下：<ul><li>1）首先，从 Cache 中，读取用户缓存。如果存在，则直接返回。</li><li>2）然后，从 DB 中，读取用户数据。如果存在，写入 Cache 中。</li><li>3）最后，返回 DB 的查询结果。</li></ul></li><li>可能会有胖友说，这里没有考虑缓存击穿、缓存穿透、缓存并发写的情况。恩，是的，但是这并不在本文的内容范围。感兴趣的，可以看看我的超哥写的 <a href="http://www.iocoder.cn/Fight/Cache-penetration-and-Cache-concurrency-and-Cache-invalidation/?self" target="_blank" rel="noopener">《缓存穿透、缓存并发、缓存失效之思路变迁》</a> 文章。</li></ul><blockquote><p>FROM <a href="https://www.ibm.com/developerworks/cn/opensource/os-cn-spring-cache/" target="_blank" rel="noopener">《注释驱动的 Spring Cache 缓存介绍》</a></p><p>Spring 3.1 引入了激动人心的基于注释（annotation）的缓存（cache）技术，它本质上不是一个具体的缓存实现方案（例如 EHCache 或者 OSCache），而是一个对缓存使用的抽象，通过在既有代码中添加少量它定义的各种 annotation，即能够达到缓存方法的返回对象的效果。</p><p>Spring 的缓存技术还具备相当的灵活性，不仅能够使用 SpEL（Spring Expression Language）来定义缓存的 key 和各种 condition，还提供开箱即用的缓存临时存储方案，也支持和主流的专业缓存例如 EHCache 集成。</p><p>其特点总结如下：</p><ul><li>通过少量的配置 annotation 注释即可使得既有代码支持缓存</li><li>支持开箱即用 Out-Of-The-Box，即不用安装和部署额外第三方组件即可使用缓存</li><li>支持 Spring Express Language，能使用对象的任何属性或者方法来定义缓存的 key 和 condition</li><li>支持 AspectJ，并通过其实现任何方法的缓存支持</li><li>支持自定义 key 和自定义缓存管理者，具有相当的灵活性和扩展性</li></ul></blockquote><ul><li>介绍有点略长，胖友耐心看看噢。</li><li>简单来说，我们可以像使用 <code>@Transactional</code> 声明式<strong>事务</strong>，使用 Spring Cache 提供的 <code>@Cacheable</code> 等注解，声明式<strong>缓存</strong>。而在实现原理上，也是基于 Spring AOP 拦截，实现缓存相关的操作。</li></ul><p>下面，我们使用 Spring Cache 将 <code>#getUser(Integer id)</code> 方法进行简化。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// UserService.java</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> UserDO <span class="title">getUser2</span><span class="params">(Integer id)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> userMapper.selectById(id);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// UserMapper.java</span></span><br><span class="line"><span class="meta">@Cacheable</span>(value = <span class="string">"users"</span>, key = <span class="string">"#id"</span>)</span><br><span class="line"><span class="function">UserDO <span class="title">selectById</span><span class="params">(Integer id)</span></span>;</span><br></pre></td></tr></table></figure><ul><li>在 UserService 的 <code>#getUser2(Integer id)</code> 方法上，我们直接调用 UserMapper ，从 DB 中查询数据。</li><li>在 UserMapper 的 <code>#selectById(Integer id)</code> 方法上，有 <code>@Cacheable</code> 注解。Spring Cache 会拦截有 <code>@Cacheable</code> 注解的方法，实现“<strong>被动写</strong>”的逻辑。</li></ul><p>是不是瞬间很清爽。下面，让我们开始愉快的入门吧。</p><h1 id="2-注解"><a href="#2-注解" class="headerlink" title="2. 注解"></a>2. 注解</h1><p>在入门 Spring Cache 之前，我们先了解下其提供的所有注解：</p><ul><li><code>@Cacheable</code></li><li><code>@CachePut</code></li><li><code>@CacheEvict</code></li><li><code>@CacheConfig</code></li><li><code>@Caching</code></li><li><code>@EnableCaching</code></li></ul><h2 id="2-1-Cacheable"><a href="#2-1-Cacheable" class="headerlink" title="2.1 @Cacheable"></a>2.1 @Cacheable</h2><p><a href="https://github.com/spring-projects/spring-framework/blob/master/spring-context/src/main/java/org/springframework/cache/annotation/Cacheable.java" target="_blank" rel="noopener">@Cacheable</a> 注解，添加在方法上，缓存方法的执行结果。执行过程如下：</p><ul><li>1）首先，判断方法执行结果的缓存。如果有，则直接返回该缓存结果。</li><li>2）然后，执行方法，获得方法结果。</li><li>3）之后，根据是否满足缓存的条件。如果满足，则缓存方法结果到缓存。</li><li>4）最后，返回方法结果。</li></ul><p><code>@Cacheable</code> 注解的<strong>常用属性</strong>，如下：</p><ul><li><code>cacheNames</code> 属性：缓存名。<strong>必填</strong>。<code>[]</code> 数组，可以填写多个缓存名。</li><li><code>values</code> 属性：和 <code>cacheNames</code> 属性相同，是它的别名。</li><li><code>key</code> 属性：缓存的 key 。允许空。<ul><li>如果为空，则默认方法的所有参数进行组合。</li><li>如果非空，则需要按照 <a href="http://itmyhome.com/spring/expressions.html" target="_blank" rel="noopener">SpEL(Spring Expression Language)</a> 来配置。例如说，<code>@Cacheable(value = &quot;users&quot;, key = &quot;#id&quot;)</code> ，使用方法参数 <code>id</code> 的值作为缓存的 key 。</li></ul></li><li><code>condition</code> 属性：基于方法<strong>入参</strong>，判断要缓存的条件。允许空。<ul><li>如果为空，则不进行入参的判断。</li><li>如果非空，则需要按照 <a href="http://itmyhome.com/spring/expressions.html" target="_blank" rel="noopener">SpEL(Spring Expression Language)</a> 来配置。例如说，<code>@Cacheable(condition=&quot;#id &gt; 0&quot;)</code> ，需要传入的 <code>id</code> 大于零。</li></ul></li><li><code>unless</code> 属性：基于方法<strong>返回</strong>，判断不缓存的条件。允许空。<ul><li>如果为空，则不进行入参的判断。</li><li>如果非空，则需要按照 <a href="http://itmyhome.com/spring/expressions.html" target="_blank" rel="noopener">SpEL(Spring Expression Language)</a> 来配置。例如说，<code>@Cacheable(unless=&quot;#result == null&quot;)</code> ，如果返回结果为 <code>null</code> ，则不进行缓存。</li><li>要注意，<code>condition</code> 和 <code>unless</code> 都是条件属性，差别在于前者针对入参，后者针对结果。</li></ul></li></ul><p><code>@Cacheable</code> 注解的<strong>不常用属性</strong>，如下：</p><ul><li><code>keyGenerator</code> 属性：自定义 key 生成器 <a href="https://github.com/spring-projects/spring-framework/blob/master/spring-context/src/main/java/org/springframework/cache/interceptor/KeyGenerator.java" target="_blank" rel="noopener">KeyGenerator</a> Bean 的名字。允许空。如果设置，则 <code>key</code> 失效。</li><li><code>cacheManager</code> 属性：自定义缓存管理器 <a href="https://github.com/spring-projects/spring-framework/blob/master/spring-context/src/main/java/org/springframework/cache/CacheManager.java" target="_blank" rel="noopener">CacheManager</a> Bean 的名字。允许空。一般不填写，除非有多个 CacheManager Bean 的情况下。</li><li><code>cacheResolver</code> 属性：自定义缓存解析器 <a href="https://github.com/spring-projects/spring-framework/blob/master/spring-context/src/main/java/org/springframework/cache/interceptor/CacheResolver.java" target="_blank" rel="noopener">CacheResolver</a> Bean 的名字。允许空。</li><li><code>sync</code> 属性，在获得不到缓存的情况下，是否同步执行方法。<ul><li>默认为 <code>false</code> ，表示无需同步。</li><li>如果设置为 <code>true</code> ，则执行方法时，会进行加锁，保证同一时刻，有且仅有一个方法在执行，其它线程阻塞等待。通过这样的方式，避免重复执行方法。注意，该功能的实现，需要参考第三方缓存的具体实现。</li></ul></li></ul><h2 id="2-2-CachePut"><a href="#2-2-CachePut" class="headerlink" title="2.2 @CachePut"></a>2.2 @CachePut</h2><p><a href="https://github.com/spring-projects/spring-framework/blob/master/spring-context/src/main/java/org/springframework/cache/annotation/CachePut.java" target="_blank" rel="noopener">@CachePut</a> 注解，添加在方法上，缓存方法的执行结果。不同于 <code>@Cacheable</code> 注解，它的执行过程如下：</p><ul><li>1）首先，执行方法，获得方法结果。<strong>也就是说，无论是否有缓存，都会执行方法</strong>。</li><li>2）然后，根据是否满足缓存的条件。如果满足，则缓存方法结果到缓存。</li><li>3）最后，返回方法结果。</li></ul><p>一般来说，使用方式如下：</p><ul><li><code>@Cacheable</code>：搭配<strong>读</strong>操作，实现缓存的<strong>被动</strong>写。</li><li><code>@CachePut</code>：配置<strong>写</strong>操作，实现缓存的<strong>主动</strong>写。</li></ul><p><code>@Cacheable</code> 注解的属性，和 <code>@Cacheable</code> 注解的属性，基本<strong>一致</strong>，只少一个 <code>sync</code> 属性。</p><h2 id="2-3-CacheEvict"><a href="#2-3-CacheEvict" class="headerlink" title="2.3 @CacheEvict"></a>2.3 @CacheEvict</h2><p><a href="https://github.com/spring-projects/spring-framework/blob/master/spring-context/src/main/java/org/springframework/cache/annotation/CacheEvict.java" target="_blank" rel="noopener">@CacheEvict</a> 注解，添加在方法上，删除缓存。</p><p>相比 <code>@CachePut</code> 注解，它额外多了两个属性：</p><ul><li><code>allEntries</code> 属性，是否删除缓存名( <code>cacheNames</code> )下，所有 key 对应的缓存。默认为 <code>false</code> ，只删除指定 key 的缓存。</li><li><code>beforeInvocation</code> 属性，是否在方法执行<strong>前</strong>删除缓存。默认为 <code>false</code> ，在方法执行<strong>后</strong>删除缓存。</li></ul><h2 id="2-4-Caching"><a href="#2-4-Caching" class="headerlink" title="2.4 @Caching"></a>2.4 @Caching</h2><p><a href="https://github.com/spring-projects/spring-framework/blob/master/spring-context/src/main/java/org/springframework/cache/annotation/CacheConfig.java" target="_blank" rel="noopener">@Caching</a> 注解，添加在方法上，可以组合使用多个 <code>@Cacheable</code>、<code>@CachePut</code>、<code>@CacheEvict</code> 注解。不太常用，可以暂时忽略。</p><h2 id="2-5-CacheConfig"><a href="#2-5-CacheConfig" class="headerlink" title="2.5 @CacheConfig"></a>2.5 @CacheConfig</h2><p><a href="https://github.com/spring-projects/spring-framework/blob/master/spring-context/src/main/java/org/springframework/cache/annotation/CacheConfig.java" target="_blank" rel="noopener">@CacheConfig</a> 注解，添加在类上，共享如下四个属性的配置：</p><ul><li><code>cacheNames</code></li><li><code>keyGenerator</code></li><li><code>cacheManager</code></li><li><code>cacheResolver</code></li></ul><p>本文只使用到 <code>cacheNames</code>，其他的属性胖友感兴趣可以自己去试一下。</p><h2 id="2-6-EnableCaching"><a href="#2-6-EnableCaching" class="headerlink" title="2.6 @EnableCaching"></a>2.6 @EnableCaching</h2><p><a href="https://github.com/spring-projects/spring-framework/blob/master/spring-context/src/main/java/org/springframework/cache/annotation/EnableCaching.java" target="_blank" rel="noopener">@EnableCaching</a> 注解，标记开启 Spring Cache 功能，所以一定要添加，不然启动不起来。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// EnableCaching.java</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">boolean</span> <span class="title">proxyTargetClass</span><span class="params">()</span> <span class="keyword">default</span> <span class="keyword">false</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function">AdviceMode <span class="title">mode</span><span class="params">()</span> <span class="keyword">default</span> AdviceMode.PROXY</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">order</span><span class="params">()</span> <span class="keyword">default</span> Ordered.LOWEST_PRECEDENCE</span>;</span><br></pre></td></tr></table></figure><h1 id="3-Spring-Boot-集成"><a href="#3-Spring-Boot-集成" class="headerlink" title="3. Spring Boot 集成"></a>3. Spring Boot 集成</h1><p>在 Spring Boot 里，提供了 <a href="https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-cache" target="_blank" rel="noopener">spring-boot-starter-cache</a> 库，实现 Spring Cache 的自动化配置，通过 <a href="https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfiguration.java" target="_blank" rel="noopener">CacheAutoConfiguration</a> 配置类。</p><p>在 Java 后端开发中，常见的缓存工具和框架列举如下：</p><ul><li>Ehcache 的功能更加丰富，</li><li>Caffeine 的性能要比 Guava LocalCache 好。</li><li>Redis 最为主流和常用。</li></ul><p>如果胖友想要了解本地缓存和分布式缓存的优缺点的对比，可以看看 <a href="http://gocwind.com/blog/2015/04/08/in-processes-vs-distributed-caching/" target="_blank" rel="noopener">《进程内缓存与分布式缓存的比较》</a> 文章。</p><p>那么，在这些缓存方案当中，<code>spring-boot-starter-cache</code> 怎么知道使用哪种呢？在默认情况下，Spring Boot 会按照如下顺序，自动判断使用哪种缓存方案，创建对应的 CacheManager 缓存管理器。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// CacheConfigurations.java</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Map&lt;CacheType, Class&lt;?&gt;&gt; MAPPINGS;</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> &#123;</span><br><span class="line">	Map&lt;CacheType, Class&lt;?&gt;&gt; mappings = <span class="keyword">new</span> EnumMap&lt;&gt;(CacheType<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line">	mappings.put(CacheType.GENERIC, GenericCacheConfiguration<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line">	mappings.put(CacheType.EHCACHE, EhCacheCacheConfiguration<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line">	mappings.put(CacheType.HAZELCAST, HazelcastCacheConfiguration<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line">	mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line">	mappings.put(CacheType.JCACHE, JCacheCacheConfiguration<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line">	mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line">	mappings.put(CacheType.REDIS, RedisCacheConfiguration<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line">	mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line">	mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line">	mappings.put(CacheType.NONE, NoOpCacheConfiguration<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line">	MAPPINGS = Collections.unmodifiableMap(mappings);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>最差的情况下，会使用 <a href="https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/SimpleCacheConfiguration.java" target="_blank" rel="noopener">SimpleCacheConfiguration</a> 。</li></ul><p>因为自动判断可能和我们希望使用的缓存方案不同，此时我们可以手动配置 <code>spring.cache.type</code> 指定类型。</p><p>考虑到目前最常使用的是 Ehcache 本地缓存，和 Redis 分布式缓存，所以我们分别在 <a href="http://www.iocoder.cn/Spring-Boot/Cache/?github#" target="_blank" rel="noopener">「4. Ehcache 示例」</a> 和 <a href="http://www.iocoder.cn/Spring-Boot/Cache/?github#" target="_blank" rel="noopener">「5. Redis 示例」</a> 小节中，一起来遨游下。</p><h1 id="4-Ehcache-示例"><a href="#4-Ehcache-示例" class="headerlink" title="4. Ehcache 示例"></a>4. Ehcache 示例</h1><blockquote><p>示例代码对应仓库：<a href="https://github.com/yuyong725/labs-springboot2/tree/master/labs-springboot2-data-access/labs-springboot2-cache/labs-springboot2-cache-ehcache" target="_blank" rel="noopener">labs-springboot2-cache-ehcache</a> 。</p></blockquote><blockquote><p>EhCache 是一个纯 Java 的进程内缓存框架，具有快速、精干等特点，是 Hibernate 中默认的 CacheProvider 。</p><p>下图是 Ehcache 在应用程序中的位置：</p><p><img src="1.png" alt="img"></p><p>主要的特性有：</p><ol><li>快速.</li><li>简单.</li><li>多种缓存策略</li><li>缓存数据有两级：内存和磁盘，因此无需担心容量问题</li><li>缓存数据会在虚拟机重启的过程中写入磁盘</li><li>可以通过 RMI、可插入 API 等方式进行分布式缓存</li><li>具有缓存和缓存管理器的侦听接口</li><li>支持多缓存管理器实例，以及一个实例的多个缓存区域</li><li>提供 Hibernate 的缓存实现</li><li>等等</li></ol></blockquote><p>下面，让我们使用 Ehcache 作为 Spring Cache 的缓存方案，开始遨游~</p><h2 id="4-1-引入依赖"><a href="#4-1-引入依赖" class="headerlink" title="4.1 引入依赖"></a>4.1 引入依赖</h2><p>在 <code>pom.xml</code> 文件中，引入相关依赖。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">"http://maven.apache.org/POM/4.0.0"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">parent</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>labs-springboot2-cache<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>cn.javadog<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.0-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">parent</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">modelVersion</span>&gt;</span>4.0.0<span class="tag">&lt;/<span class="name">modelVersion</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>labs-springboot2-cache-ehcache<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- 实现对 Caches 的自动化配置 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-cache<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- Ehcache 依赖 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>net.sf.ehcache<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>ehcache<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- 方便等会写单元测试 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-test<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">scope</span>&gt;</span>test<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.projectlombok<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>lombok<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure><p>具体每个依赖的作用，胖友自己认真看下添加的所有注释噢。</p><h2 id="4-2-应用配置文件"><a href="#4-2-应用配置文件" class="headerlink" title="4.2 应用配置文件"></a>4.2 应用配置文件</h2><p>在 <code>resources</code> 目录下，创建 <code>application.yaml</code> 配置文件。配置如下：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">cache:</span></span><br><span class="line">    <span class="attr">ehcache:</span></span><br><span class="line">      <span class="attr">config:</span> <span class="string">classpath:ehcache.xml</span> <span class="comment"># 默认就是该位置，无须配置，除非自定义了其他命名</span></span><br><span class="line">    <span class="attr">type:</span> <span class="string">ehcache</span></span><br></pre></td></tr></table></figure><ul><li><code>spring.datasource</code> 配置项下，设置数据源相关的配置。</li><li><code>spring.cache</code>配置项下，设置 Cache 相关的配置。<ul><li><code>type</code> 属性，设置 Cache 使用方案为 Ehcache 。</li></ul></li><li><code>logging</code> 配置项，设置打印 SQL 日志，方便我们查看是否读取了 DB 。</li></ul><h2 id="4-3-Ehcache-配置文件"><a href="#4-3-Ehcache-配置文件" class="headerlink" title="4.3 Ehcache 配置文件"></a>4.3 Ehcache 配置文件</h2><p>在 <code>resources</code> 目录下，创建 <code>ehcache.xml</code> 配置文件。配置如下：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">ehcache</span> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xsi:noNamespaceSchemaLocation</span>=<span class="string">"http://ehcache.org/ehcache.xsd"</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!-- name：缓存名 --&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- maxElementsInMemory：最大缓存 key 数量 --&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- timeToLiveSeconds：缓存过期时长，单位：秒 --&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- memoryStoreEvictionPolicy：缓存淘汰策略 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">cache</span> <span class="attr">name</span>=<span class="string">"users"</span></span></span><br><span class="line"><span class="tag">           <span class="attr">maxElementsInMemory</span>=<span class="string">"1000"</span></span></span><br><span class="line"><span class="tag">           <span class="attr">timeToLiveSeconds</span>=<span class="string">"60"</span></span></span><br><span class="line"><span class="tag">           <span class="attr">memoryStoreEvictionPolicy</span>=<span class="string">"LRU"</span>/&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">ehcache</span>&gt;</span></span><br></pre></td></tr></table></figure><ul><li>我们配置了一个名字为 <code>users</code> 的缓存。后续，我们会使用到。</li></ul><h2 id="4-4-EhcacheApplication"><a href="#4-4-EhcacheApplication" class="headerlink" title="4.4 EhcacheApplication"></a>4.4 EhcacheApplication</h2><p>创建 <code>EhcacheApplication</code> 类，代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// EhcacheApplication.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableCaching</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">EhcacheApplication</span> </span>&#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>配置 <code>@EnableCaching</code> 注解，开启 Spring Cache 功能。</li><li>配置 <code>@MapperScan</code> 注解，扫描对应 Mapper 接口所在的包路径。</li></ul><h2 id="4-5-UserDO"><a href="#4-5-UserDO" class="headerlink" title="4.5 UserDO"></a>4.5 UserDO</h2><p>创建 <code>UserDO</code> 类，用户 DO 。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// UserDO.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="meta">@Accessors</span>(chain = <span class="keyword">true</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserDO</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 用户编号</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> Integer id;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 账号</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> String username;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 密码（明文）</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * ps：生产环境下，千万不要明文噢</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> String password;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 创建时间</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> Date createTime;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 是否删除</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> Integer deleted;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">toString</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"UserDO&#123;"</span> +</span><br><span class="line">                <span class="string">"id="</span> + id +</span><br><span class="line">                <span class="string">", username='"</span> + username + <span class="string">'\''</span> +</span><br><span class="line">                <span class="string">", password='"</span> + password + <span class="string">'\''</span> +</span><br><span class="line">                <span class="string">", createTime="</span> + createTime +</span><br><span class="line">                <span class="string">", deleted="</span> + deleted +</span><br><span class="line">                <span class="string">'&#125;'</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对应的创建表的 SQL 如下：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> <span class="string">`users`</span> (</span><br><span class="line">  <span class="string">`id`</span> <span class="built_in">int</span>(<span class="number">11</span>) <span class="keyword">NOT</span> <span class="literal">NULL</span> AUTO_INCREMENT <span class="keyword">COMMENT</span> <span class="string">'用户编号'</span>,</span><br><span class="line">  <span class="string">`username`</span> <span class="built_in">varchar</span>(<span class="number">64</span>) <span class="keyword">COLLATE</span> utf8mb4_bin <span class="keyword">DEFAULT</span> <span class="literal">NULL</span> <span class="keyword">COMMENT</span> <span class="string">'账号'</span>,</span><br><span class="line">  <span class="string">`password`</span> <span class="built_in">varchar</span>(<span class="number">32</span>) <span class="keyword">COLLATE</span> utf8mb4_bin <span class="keyword">DEFAULT</span> <span class="literal">NULL</span> <span class="keyword">COMMENT</span> <span class="string">'密码'</span>,</span><br><span class="line">  <span class="string">`create_time`</span> datetime <span class="keyword">DEFAULT</span> <span class="literal">NULL</span> <span class="keyword">COMMENT</span> <span class="string">'创建时间'</span>,</span><br><span class="line">  <span class="string">`deleted`</span> <span class="built_in">bit</span>(<span class="number">1</span>) <span class="keyword">DEFAULT</span> <span class="literal">NULL</span> <span class="keyword">COMMENT</span> <span class="string">'是否删除。0-未删除；1-删除'</span>,</span><br><span class="line">  PRIMARY <span class="keyword">KEY</span> (<span class="string">`id`</span>),</span><br><span class="line">  <span class="keyword">UNIQUE</span> <span class="keyword">KEY</span> <span class="string">`idx_username`</span> (<span class="string">`username`</span>)</span><br><span class="line">) <span class="keyword">ENGINE</span>=<span class="keyword">InnoDB</span> AUTO_INCREMENT=<span class="number">7</span> <span class="keyword">DEFAULT</span> <span class="keyword">CHARSET</span>=utf8mb4 <span class="keyword">COLLATE</span>=utf8mb4_bin;</span><br><span class="line"></span><br><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> <span class="string">`users`</span>(<span class="string">`id`</span>, <span class="string">`username`</span>, <span class="string">`password`</span>, <span class="string">`create_time`</span>, <span class="string">`deleted`</span>) <span class="keyword">VALUES</span> (<span class="number">1</span>, <span class="string">'yudaoyuanma'</span>, <span class="string">'buzhidao'</span>, <span class="keyword">now</span>(), <span class="number">0</span>);</span><br></pre></td></tr></table></figure><h2 id="4-6-UserMapper"><a href="#4-6-UserMapper" class="headerlink" title="4.6 UserMapper"></a>4.6 UserMapper</h2><p>创建 <code>UserMapper</code> 接口。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// UserMapper.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Slf</span>4j</span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@CacheConfig</span>(cacheNames = <span class="string">"users"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserMapper</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    Map&lt;Integer, UserDO&gt; CACHE_MAP = <span class="keyword">new</span> HashMap&lt;&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">UserMapper</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        CACHE_MAP.put(<span class="number">1</span>,</span><br><span class="line">            <span class="keyword">new</span> UserDO()</span><br><span class="line">                .setId(<span class="number">1</span>)</span><br><span class="line">                .setUsername(UUID.randomUUID().toString())</span><br><span class="line">                .setPassword(<span class="string">"pwd"</span>)</span><br><span class="line">                .setCreateTime(<span class="keyword">new</span> Date())</span><br><span class="line">                .setDeleted(<span class="number">0</span>));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Cacheable</span>(key = <span class="string">"#id"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> UserDO <span class="title">selectById</span><span class="params">(Integer id)</span></span>&#123;</span><br><span class="line">        log.info(<span class="string">"=====================&gt; 查询id为[&#123;&#125;]的数据，走的数据库"</span>, id);</span><br><span class="line">        <span class="keyword">return</span> CACHE_MAP.get(id);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@CachePut</span>(key = <span class="string">"#user.id"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> UserDO <span class="title">insert</span><span class="params">(UserDO user)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 插入记录</span></span><br><span class="line">        CACHE_MAP.put(user.getId(), user);</span><br><span class="line">        log.info(<span class="string">"=====================&gt; 插入id为[&#123;&#125;]的数据到数据库"</span>, user.getId());</span><br><span class="line">        <span class="comment">// 返回用户</span></span><br><span class="line">        <span class="keyword">return</span> user;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@CacheEvict</span>(key = <span class="string">"#id"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">deleteById</span><span class="params">(Integer id)</span></span>&#123;</span><br><span class="line">        log.info(<span class="string">"=====================&gt; 从数据库删除id为[&#123;&#125;]的数据"</span>, id);</span><br><span class="line">        <span class="keyword">return</span> CACHE_MAP.remove(id) == <span class="keyword">null</span> ? <span class="number">0</span> : <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>这里模拟的假数据库<code>CACHE_MAP</code>，初始化时添加一个ID为1的值进去，并通过<code>@Component</code>交给Spring管理。</li><li>在类上，我们添加了 <code>@CacheConfig(cacheNames = &quot;users&quot;)</code> 注解，统一配置该 UserMapper 使用的缓存名为 <code>users</code> 。</li><li><code>#selectById(Integer id)</code> 方法，添加了 <code>@Cacheable(key = &quot;#id&quot;)</code> 注解，优先读缓存。<ul><li>注意，ORM插入方法一般返回的是 <code>int</code> 结果，无法进行缓存，<code>@CachePut</code>添加的是方法的返回值。</li></ul></li><li><code>#deleteById(Integer id)</code> 方法，添加了 <code>@CacheEvict(key = &quot;#id&quot;)</code> 注解，删除缓存。</li></ul><h2 id="4-7-UserMapperTest"><a href="#4-7-UserMapperTest" class="headerlink" title="4.7 UserMapperTest"></a>4.7 UserMapperTest</h2><p>创建 <code>UserMapperTest</code> 测试类，我们来测试一下简单的 UserMapper 的每个操作。核心代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// UserMapperTest.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Slf</span>4j</span><br><span class="line"><span class="meta">@RunWith</span>(SpringRunner<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line"><span class="class">@<span class="title">SpringBootTest</span>(<span class="title">classes</span> </span>= Application<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line"><span class="class"><span class="title">public</span> <span class="title">class</span> <span class="title">UserMapperTest</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String CACHE_NAME_USER = <span class="string">"users"</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserMapper userMapper;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> CacheManager cacheManager;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// ... 省略每个单元测试。</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>① <code>#testCacheManager()</code></strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// UserMapperTest.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testCacheManager</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    System.out.println(cacheManager);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p>目的，是查看 <code>cacheManager</code> 的类型。执行日志如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">org.springframework.cache.ehcache.EhCacheCacheManager@110844f6</span><br></pre></td></tr></table></figure><ul><li>可以确认是 EhCache 对应的缓存管理器。</li></ul></li></ul><p><strong>② <code>#testSelectById()</code></strong></p><p>本测试用例，是为了演示 <code>@Cacheable</code> 注解的用途。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; UserMapperTest.java</span><br><span class="line"></span><br><span class="line">@Test</span><br><span class="line">public void testSelectById() &#123;</span><br><span class="line">    &#x2F;&#x2F; userMapper 启动时已经放进去了一条ID为1的数据</span><br><span class="line">    Integer id &#x3D; 1;</span><br><span class="line">    &#x2F;&#x2F; &lt;1.1&gt; 查询 id &#x3D; 1 的记录</span><br><span class="line">    UserDO user &#x3D; userMapper.selectById(id);</span><br><span class="line">    log.info(&quot;user：&#123;&#125;&quot;, user);</span><br><span class="line">    &#x2F;&#x2F; &lt;1.2&gt; 判断缓存中，是不是存在</span><br><span class="line">    Assert.assertNotNull(&quot;缓存为空&quot;, cacheManager.getCache(CACHE_NAME_USER).get(user.getId(), UserDO.class));</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; &lt;2&gt; 查询 id &#x3D; 1 的记录</span><br><span class="line">    user &#x3D; userMapper.selectById(id);</span><br><span class="line">    log.info(&quot;user：&#123;&#125;&quot;, user);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p><code>&lt;1.1&gt;</code> 处，首次查询 <code>id = 1</code> 的记录。这里，我们是为了触发从 DB 中查询该记录，然后缓存起来。执行日志如下：</p><figure class="highlight verilog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">2020</span>-<span class="number">01</span>-<span class="number">09</span> <span class="number">12</span>:<span class="number">20</span>:<span class="number">59</span><span class="variable">.392</span>  INFO <span class="number">22596</span> --- [           main] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.cache</span><span class="variable">.ehcache</span><span class="variable">.mapper</span><span class="variable">.UserMapper</span>  : =====================&gt; 查询id为[<span class="number">1</span>]的数据，走的数据库</span><br><span class="line"><span class="number">2020</span>-<span class="number">01</span>-<span class="number">09</span> <span class="number">12</span>:<span class="number">20</span>:<span class="number">59</span><span class="variable">.396</span>  INFO <span class="number">22596</span> --- [           main] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.cache</span><span class="variable">.ehcache</span><span class="variable">.UserMapperTest</span>     : user：UserDO&#123;id=<span class="number">1</span>, username='<span class="number">69</span>c90e96-<span class="number">6</span>f99-<span class="number">4</span>e78-<span class="number">8</span>c5b-ed07b4592096', password='pwd', createTime=Thu Jan <span class="number">09</span> <span class="number">12</span>:<span class="number">20</span>:<span class="number">58</span> CST <span class="number">2020</span>, deleted=<span class="number">0</span>&#125;</span><br></pre></td></tr></table></figure><ul><li>这里，我们执行查询了一次 DB 。</li></ul></li><li><p><code>&lt;1.2&gt;</code> 处，我们从 CacheManager 中，查询该记录缓存，然后通过 Assert 断言该记录的缓存存在。</p></li><li><p><code>&lt;2&gt;</code> 处，再次查询 <code>id = 1</code> 的记录。这里，我们不会从 DB 查询，直接走缓存返回即可。执行日志如下：</p><figure class="highlight verilog"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">2020</span>-<span class="number">01</span>-<span class="number">09</span> <span class="number">12</span>:<span class="number">20</span>:<span class="number">59</span><span class="variable">.397</span>  INFO <span class="number">22596</span> --- [           main] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.cache</span><span class="variable">.ehcache</span><span class="variable">.UserMapperTest</span>     : user：UserDO&#123;id=<span class="number">1</span>, username='<span class="number">69</span>c90e96-<span class="number">6</span>f99-<span class="number">4</span>e78-<span class="number">8</span>c5b-ed07b4592096', password='pwd', createTime=Thu Jan <span class="number">09</span> <span class="number">12</span>:<span class="number">20</span>:<span class="number">58</span> CST <span class="number">2020</span>, deleted=<span class="number">0</span>&#125;</span><br></pre></td></tr></table></figure><ul><li>这里，我们只查询了一次 Cache 。</li></ul></li></ul><p><strong>③ <code>#testInsert()</code></strong></p><p>本测试用例，是为了演示 <code>@CachePut</code> 注解的用途。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// UserMapperTest.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testInsert</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">// &lt;1&gt; 插入记录</span></span><br><span class="line">    UserDO user = <span class="keyword">new</span> UserDO()</span><br><span class="line">      .setId(<span class="number">2</span>)</span><br><span class="line">      .setUsername(UUID.randomUUID().toString())</span><br><span class="line">      .setPassword(<span class="string">"pwd"</span>)</span><br><span class="line">      .setCreateTime(<span class="keyword">new</span> Date())</span><br><span class="line">      .setDeleted(<span class="number">0</span>);</span><br><span class="line">    userMapper.insert(user);</span><br><span class="line"></span><br><span class="line">		<span class="comment">// &lt;2&gt; 判断缓存中，是不是存在</span></span><br><span class="line">		Assert.assertNotNull(<span class="string">"缓存为空"</span>, cacheManager.getCache(CACHE_NAME_USER).get(user.getId(), UserDO<span class="class">.<span class="keyword">class</span>))</span>;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p><code>&lt;1&gt;</code> 处，插入一条 <code>users</code> 的记录。这里，我们是为了触发主动写入该记录到缓存中。</p></li><li><p><code>&lt;2&gt;</code> 处，我们从 CacheManager 中，查询该记录缓存，然后通过 Assert 断言该记录的缓存存在。执行日志如下：</p><figure class="highlight verilog"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">2020</span>-<span class="number">01</span>-<span class="number">09</span> <span class="number">12</span>:<span class="number">25</span>:<span class="number">14</span><span class="variable">.394</span>  INFO <span class="number">22610</span> --- [           main] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.cache</span><span class="variable">.ehcache</span><span class="variable">.mapper</span><span class="variable">.UserMapper</span>  : =====================&gt; 插入id为[<span class="number">2</span>]的数据到数据库</span><br></pre></td></tr></table></figure><ul><li>这里会先向 DB 中添加一条记录，再将方法返回的结果添加到缓存中。</li></ul></li></ul><p><strong>④ <code>#deleteById()</code></strong></p><p>本测试用例，是为了演示 <code>@CacheEvict</code> 注解的用途。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// UserMapperTest.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testDeleteById</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">// &lt;1&gt; 插入记录，为了让缓存里有记录</span></span><br><span class="line">    testInsert();</span><br><span class="line">    <span class="comment">// &lt;2.1&gt; 删除记录，为了让缓存被删除</span></span><br><span class="line">    userMapper.deleteById(<span class="number">2</span>);</span><br><span class="line">    <span class="comment">// &lt;2.2&gt; 判断缓存中，是不是存在</span></span><br><span class="line">    Assert.assertNull(<span class="string">"缓存不为空"</span>, cacheManager.getCache(CACHE_NAME_USER).get(<span class="number">2</span>, UserDO<span class="class">.<span class="keyword">class</span>))</span>;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><code>&lt;1&gt;</code> 和 <code>#testInsert()</code> 方法是一致的。此时，刚插入的一条 <code>users</code> 的记录在缓存中。</li><li><code>&lt;2.1&gt;</code> 处，删除刚插入的那条 <code>users</code> 的记录。这里，我们是为了触发从 Cache 中删除该记录的。</li><li><code>&lt;2.2&gt;</code> 处，我们从 CacheManager 中，查询该记录缓存，然后通过 Assert 断言该记录的缓存不存在。</li></ul><h1 id="5-Redis-示例"><a href="#5-Redis-示例" class="headerlink" title="5. Redis 示例"></a>5. Redis 示例</h1><blockquote><p>示例代码对应仓库：<a href="https://github.com/yuyong725/labs-springboot2/tree/master/labs-springboot2-data-access/labs-springboot2-cache/labs-springboot2-cache-redis" target="_blank" rel="noopener">labs-springboot2-cache-redis</a> 。</p></blockquote><p>本小节，我们的整体示例，和 <a href="http://www.iocoder.cn/Spring-Boot/Cache/?github#" target="_blank" rel="noopener">「4. Ehcache」</a> 是一致的。</p><h2 id="5-1-引入依赖"><a href="#5-1-引入依赖" class="headerlink" title="5.1 引入依赖"></a>5.1 引入依赖</h2><p>在 <code>pom.xml</code> 文件中，引入相关依赖。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">"http://maven.apache.org/POM/4.0.0"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">parent</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>labs-springboot2-cache<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>cn.javadog<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.0-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">parent</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">modelVersion</span>&gt;</span>4.0.0<span class="tag">&lt;/<span class="name">modelVersion</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>labs-springboot2-cache-redis<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- 实现对 Caches 的自动化配置 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-cache<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- 实现对 Spring Data Redis 的自动化配置 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-data-redis<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">exclusions</span>&gt;</span></span><br><span class="line">                <span class="comment">&lt;!-- 去掉对 Lettuce 的依赖，因为 Spring Boot 优先使用 Lettuce 作为 Redis 客户端 --&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">exclusion</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>io.lettuce<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>lettuce-core<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">exclusion</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">exclusions</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- 引入 Jedis 的依赖，这样 Spring Boot 实现对 Jedis 的自动化配置 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>redis.clients<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>jedis<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- 方便等会写单元测试 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-test<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">scope</span>&gt;</span>test<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.projectlombok<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>lombok<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure><p>具体每个依赖的作用，胖友自己认真看下添加的所有注释。</p><p>Spring Data 使用 Redis 作为缓存的方案的时候，底层使用的是 Spring Data 提供的 <a href="https://github.com/spring-projects/spring-data-redis/blob/master/src/main/java/org/springframework/data/redis/core/RedisTemplate.java" target="_blank" rel="noopener">RedisTemplate</a> ，所以我们引入 <code>spring-boot-starter-data-redis</code> 依赖，实现对 RedisTemplate 的自动化配置。</p><h2 id="5-2-应用配置文件"><a href="#5-2-应用配置文件" class="headerlink" title="5.2 应用配置文件"></a>5.2 应用配置文件</h2><p>在 <code>resources</code> 目录下，创建 <code>application.yaml</code> 配置文件。配置如下：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">cache:</span></span><br><span class="line">    <span class="attr">type:</span> <span class="string">redis</span></span><br><span class="line">    <span class="attr">redis:</span></span><br><span class="line">      <span class="attr">key-prefix:</span> <span class="string">"labs:cache:redis:"</span> <span class="comment"># 指定key的前缀，会与 @Cacheable 连接</span></span><br><span class="line">      <span class="attr">use-key-prefix:</span> <span class="literal">true</span> <span class="comment"># 开启key的前缀，不开启的话，默认会使用 @CacheConfig 的 cacheNames</span></span><br><span class="line">      <span class="attr">time-to-live:</span> <span class="string">60s</span> <span class="comment"># key的过期时长设置为60s，不设置默认永不过期</span></span><br><span class="line">  <span class="comment"># 对应 RedisProperties 类</span></span><br><span class="line">  <span class="attr">redis:</span></span><br><span class="line">    <span class="attr">host:</span> <span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span></span><br><span class="line">    <span class="attr">port:</span> <span class="number">6379</span></span><br><span class="line">    <span class="attr">password:</span> <span class="comment"># Redis 服务器密码，默认为空。生产中，一定要设置 Redis 密码！</span></span><br><span class="line">    <span class="attr">database:</span> <span class="number">0</span> <span class="comment"># Redis 数据库号，默认为 0 。</span></span><br><span class="line">    <span class="attr">timeout:</span> <span class="number">0</span> <span class="comment"># Redis 连接超时时间，单位：毫秒。</span></span><br><span class="line">    <span class="comment"># 对应 RedisProperties.Jedis 内部类</span></span><br><span class="line">    <span class="attr">jedis:</span></span><br><span class="line">      <span class="attr">pool:</span></span><br><span class="line">        <span class="attr">max-active:</span> <span class="number">8</span> <span class="comment"># 连接池最大连接数，默认为 8 。使用负数表示没有限制。</span></span><br><span class="line">        <span class="attr">max-idle:</span> <span class="number">8</span> <span class="comment"># 默认连接数最小空闲的连接数，默认为 8 。使用负数表示没有限制。</span></span><br><span class="line">        <span class="attr">min-idle:</span> <span class="number">0</span> <span class="comment"># 默认连接池最小空闲的连接数，默认为 0 。允许设置 0 和 正数。</span></span><br><span class="line">        <span class="attr">max-wait:</span> <span class="number">-1</span> <span class="comment"># 连接池最大阻塞等待时间，单位：毫秒。默认为 -1 ，表示不限制。</span></span><br></pre></td></tr></table></figure><ul><li><code>spring.datasource</code> 配置项下，设置数据源相关的配置。</li><li><code>spring.cache</code> 配置项下，设置 Cache 相关的配置。<ul><li><code>type</code> 属性，设置 Cache 使用方案为 Redis 。</li><li><code>redis</code> 属性，设置 redis 的缓存属性，具体含义见上面的注释</li></ul></li><li><code>spring.redis</code> 配置项下，设置 Spring Data Redis 相关的配置。如果没有使用过 Spring Data Redis 的胖友，不用慌，照着改就好。当然，也欢迎阅读 <a href="http://www.iocoder.cn/Spring-Boot/Redis/?self" target="_blank" rel="noopener">《芋道 Spring Boot Redis 入门》</a> 文章。</li><li><code>mybatis-plus</code> 配置项下，设置 MyBatis-Plus 相关的配置。如果没有使用过 MyBatis-Plus 的胖友，不用慌，照着改就好。当然，也欢迎阅读 <a href="http://www.iocoder.cn/Spring-Boot/MyBatis/?self" target="_blank" rel="noopener">《芋道 Spring Boot MyBatis 入门》</a> 文章。</li><li><code>logging</code> 配置项，设置打印 SQL 日志，方便我们查看是否读取了 DB 。</li></ul><h2 id="5-3-RedisApplication"><a href="#5-3-RedisApplication" class="headerlink" title="5.3 RedisApplication"></a>5.3 RedisApplication</h2><p>和 <a href="https://calm_java.gitee.io/blog/2020/01/09/labs-springboot2-cache/">「4.4 EhcacheApplication」</a> 一致。</p><h2 id="5-4-UserDO"><a href="#5-4-UserDO" class="headerlink" title="5.4 UserDO"></a>5.4 UserDO</h2><p>和 <a href="https://calm_java.gitee.io/blog/2020/01/09/labs-springboot2-cache/">「4.5 UserDO」</a> 一致。差别在于，需要让 <code>UserDO</code> 实现 Serializable 接口。因为，我们需要将 UserDO 序列化后，才能存储到 Redis 中。</p><h2 id="5-5-UserMapper"><a href="#5-5-UserMapper" class="headerlink" title="5.5 UserMapper"></a>5.5 UserMapper</h2><p>和 <a href="http://www.iocoder.cn/Spring-Boot/Cache/?github#" target="_blank" rel="noopener">「4.6 UserMapper」</a> 一致。</p><h2 id="5-6-UserMapperTest"><a href="#5-6-UserMapperTest" class="headerlink" title="5.6 UserMapperTest"></a>5.6 UserMapperTest</h2><p>和 <a href="https://calm_java.gitee.io/blog/2020/01/09/labs-springboot2-cache/">「4.7 UserMapperTest」</a> 基本一致。为了结合 Redis 中的数据一起说，所以这里就再“重复”一遍。</p><p>创建 <code>UserMapperTest</code> 测试类，我们来测试一下简单的 UserMapper 的每个操作。核心代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// UserMapperTest.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@RunWith</span>(SpringRunner<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line"><span class="class">@<span class="title">SpringBootTest</span>(<span class="title">classes</span> </span>= Application<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line"><span class="class"><span class="title">public</span> <span class="title">class</span> <span class="title">UserMapperTest</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String CACHE_NAME_USER = <span class="string">"users"</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserMapper userMapper;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> CacheManager cacheManager;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// ... 省略每个单元测试。</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>① <code>#testCacheManager()</code></strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// UserMapperTest.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testCacheManager</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    System.out.println(cacheManager);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p>目的，是查看 <code>cacheManager</code> 的类型。执行日志如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">org.springframework.data.redis.cache.RedisCacheManager@51751e5f</span><br></pre></td></tr></table></figure><ul><li>可以确认是 Redis 对应的缓存管理器。</li></ul></li></ul><blockquote><p>注意，每一轮的测试，都使用 <a href="https://redis.io/commands/flushdb" target="_blank" rel="noopener"><code>FLUSHDB</code></a> 指令，将 Redis 清空下。</p></blockquote><p><strong>② <code>#testSelectById()</code></strong></p><p>本测试用例，是为了演示 <code>@Cacheable</code> 注解的用途。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// UserMapperTest.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testSelectById</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">// userMapper 启动时已经放进去了一条ID为1的数据</span></span><br><span class="line">    Integer id = <span class="number">1</span>;</span><br><span class="line">    <span class="comment">// &lt;1.1&gt; 查询 id = 1 的记录</span></span><br><span class="line">    UserDO user = userMapper.selectById(id);</span><br><span class="line">    log.info(<span class="string">"user：&#123;&#125;"</span>, user);</span><br><span class="line">    <span class="comment">// &lt;1.2&gt; 判断缓存中，是不是存在</span></span><br><span class="line">    Assert.assertNotNull(<span class="string">"缓存为空"</span>, cacheManager.getCache(CACHE_NAME_USER).get(user.getId(), UserDO<span class="class">.<span class="keyword">class</span>))</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// &lt;2&gt; 查询 id = 1 的记录</span></span><br><span class="line">    user = userMapper.selectById(id);</span><br><span class="line">    log.info(<span class="string">"user：&#123;&#125;"</span>, user);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p><code>&lt;1.1&gt;</code> 处，首次查询 <code>id = 1</code> 的记录。这里，我们是为了触发从 DB 中查询该记录，然后缓存起来。执行日志如下：</p><figure class="highlight verilog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">2020</span>-<span class="number">01</span>-<span class="number">09</span> <span class="number">12</span>:<span class="number">59</span>:<span class="number">09</span><span class="variable">.597</span>  INFO <span class="number">22726</span> --- [           main] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.cache</span><span class="variable">.redis</span><span class="variable">.mapper</span><span class="variable">.UserMapper</span>    : =====================&gt; 查询id为[<span class="number">1</span>]的数据，走的数据库</span><br><span class="line"><span class="number">2020</span>-<span class="number">01</span>-<span class="number">09</span> <span class="number">12</span>:<span class="number">59</span>:<span class="number">09</span><span class="variable">.604</span>  INFO <span class="number">22726</span> --- [           main] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.cache</span><span class="variable">.redis</span><span class="variable">.UserMapperTest</span>       : user：UserDO&#123;id=<span class="number">1</span>, username='<span class="number">0</span>ab909e3-<span class="number">5807</span>-<span class="number">401</span>d-a51f-de61ea2bd399', password='pwd', createTime=Thu Jan <span class="number">09</span> <span class="number">12</span>:<span class="number">59</span>:<span class="number">08</span> CST <span class="number">2020</span>, deleted=<span class="number">0</span>&#125;</span><br><span class="line"><span class="number">2020</span>-<span class="number">01</span>-<span class="number">09</span> <span class="number">12</span>:<span class="number">59</span>:<span class="number">09</span><span class="variable">.612</span>  INFO <span class="number">22726</span> --- [           main] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.cache</span><span class="variable">.redis</span><span class="variable">.UserMapperTest</span>       : user：UserDO&#123;id=<span class="number">1</span>, username='<span class="number">0</span>ab909e3-<span class="number">5807</span>-<span class="number">401</span>d-a51f-de61ea2bd399', password='pwd', createTime=Thu Jan <span class="number">09</span> <span class="number">12</span>:<span class="number">59</span>:<span class="number">08</span> CST <span class="number">2020</span>, deleted=<span class="number">0</span>&#125;</span><br></pre></td></tr></table></figure><ul><li>这里，我们执行查询了一次 DB 。</li></ul></li><li><p><code>&lt;1.2&gt;</code> 处，我们从 CacheManager 中，查询该记录缓存，然后通过 Assert 断言该记录的缓存存在。卡姆使用redis客户端<code>rdm</code>查看如下：</p><p><img src="2.png" alt="rdm"></p><ul><li>可以看到 <code>labs:cache:redis:1</code> 这个 Redis key，并且过期时间和配置文件中设置的一致。说明 RedisCacheManager 是将配置文件中的 <code>spring.cache.redis.key-prefix</code> + <code>@Cacheable#key</code> 拼接在一起。<ul><li>如果没有开启 <code>spring.cache.redis.use-key-prefix</code>属性，RedisCacheManager 将使用 <code>::</code> 将 <code>cacheNames</code> 和 <code>key</code> 作为分隔符后，拼接在一起，作为 Redis key， 如<code>users::1</code>。</li></ul></li><li><code>labs:cache:redis:1</code> 对应的值，是一串“乱七八糟”的字符，是因为 RedisTemplate 默认使用 <a href="https://juejin.im/post/5ce3cdc8e51d45777b1a3cdf" target="_blank" rel="noopener">Java 序列化</a>方式，所以看到的值才是这样的。实际在使用时，我们可以修改 RedisTemplate 的序列化方式为 <a href="http://www.iocoder.cn/Spring-Boot/Redis/?github" target="_blank" rel="noopener">JSON 的序列化方式</a>。</li></ul></li><li><p><code>&lt;2&gt;</code> 处，再次查询 <code>id = 1</code> 的记录。这里，我们不会从 DB 查询，直接走缓存返回即可。执行日志如下：</p><figure class="highlight verilog"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">2020</span>-<span class="number">01</span>-<span class="number">09</span> <span class="number">13</span>:<span class="number">03</span>:<span class="number">12</span><span class="variable">.576</span>  INFO <span class="number">22740</span> --- [           main] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.cache</span><span class="variable">.redis</span><span class="variable">.UserMapperTest</span>       : user：UserDO&#123;id=<span class="number">1</span>, username='a1341998-dec7-<span class="number">43</span>e3-<span class="number">9</span>df4-<span class="number">391</span>f74a0a718', password='pwd', createTime=Thu Jan <span class="number">09</span> <span class="number">13</span>:<span class="number">03</span>:<span class="number">11</span> CST <span class="number">2020</span>, deleted=<span class="number">0</span>&#125;</span><br></pre></td></tr></table></figure><ul><li>这里，我们只查询了一次 Cache 。</li></ul></li></ul><p><strong>③ <code>#testInsert()</code></strong></p><p>本测试用例，是为了演示 <code>@CachePut</code> 注解的用途。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// UserMapperTest.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testInsert</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">// &lt;1&gt; 插入记录</span></span><br><span class="line">    UserDO user = <span class="keyword">new</span> UserDO()</span><br><span class="line">        .setId(<span class="number">2</span>)</span><br><span class="line">        .setUsername(UUID.randomUUID().toString())</span><br><span class="line">        .setPassword(<span class="string">"pwd"</span>)</span><br><span class="line">        .setCreateTime(<span class="keyword">new</span> Date())</span><br><span class="line">        .setDeleted(<span class="number">0</span>);</span><br><span class="line">    userMapper.insert(user);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// &lt;2&gt; 判断缓存中，是不是存在</span></span><br><span class="line">    Assert.assertNotNull(<span class="string">"缓存为空"</span>, cacheManager.getCache(CACHE_NAME_USER).get(user.getId(), UserDO<span class="class">.<span class="keyword">class</span>))</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p><code>&lt;1&gt;</code> 处，插入一条 <code>users</code> 的记录。这里，我们是为了触发主动写入该记录到缓存中。</p></li><li><p><code>&lt;2&gt;</code> 处，我们从 CacheManager 中，查询该记录缓存，然后通过 Assert 断言该记录的缓存存在。使用redis图形化界面工具<code>rdm</code>查看如下：</p><p><img src="3.png" alt="rdm"></p></li></ul><p><strong>④ <code>#deleteById()</code></strong></p><p>本测试用例，是为了演示 <code>@CacheEvict</code> 注解的用途。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// UserMapperTest.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testDeleteById</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">// &lt;1&gt; 插入记录，为了让缓存里有记录</span></span><br><span class="line">    testInsert();</span><br><span class="line">    <span class="comment">// &lt;3.1&gt; 删除记录，为了让缓存被删除</span></span><br><span class="line">    userMapper.deleteById(<span class="number">2</span>);</span><br><span class="line">    <span class="comment">// &lt;3.2&gt; 判断缓存中，是不是存在</span></span><br><span class="line">    Assert.assertNull(<span class="string">"缓存不为空"</span>, cacheManager.getCache(CACHE_NAME_USER).get(<span class="number">2</span>, UserDO<span class="class">.<span class="keyword">class</span>))</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p><code>&lt;1&gt;</code> 和 <code>#testInsert()</code> 方法是一致的。此时，刚插入的一条 <code>users</code> 的记录在缓存中。</p></li><li><p><code>&lt;3.1&gt;</code> 处，删除刚插入的那条 <code>users</code> 的记录。这里，我们是为了触发从 Cache 中删除该记录的。</p></li><li><p><code>&lt;3.2&gt;</code> 处，我们从 CacheManager 中，查询该记录缓存，然后通过 Assert 断言该记录的缓存不存在。我们进入 Reids客户端<code>rdm</code>，查看下缓存情况。如下：</p><p><img src="4.png" alt=""></p><ul><li>可以看到redis缓存已经没有了，但务必及时查看，因为卡姆设置的缓存有效期是60s，也可能是因为过期导致。</li></ul></li></ul><h2 id="5-7-过期时间"><a href="#5-7-过期时间" class="headerlink" title="5.7 过期时间"></a>5.7 过期时间</h2><p>在 Spring Data 使用 Redis 作为缓存方案时，默认情况下是<strong>永不过期</strong>的。</p><p>虽然说，我们可以通 <code>spring.cache.redis.time-to-live</code> 配置项，设置过期时间。但是，它是<strong>全局的统一</strong>的。这样在实际使用时，是无法满足我们希望不同的缓存，使用不同的过期时间。</p><p>不过我们如果翻看 <a href="https://github.com/spring-projects/spring-data-redis/blob/master/src/main/java/org/springframework/data/redis/cache/RedisCacheManager.java" target="_blank" rel="noopener">RedisCacheManager</a> 的源码，我们又会发现有个 <code>initialCacheConfiguration</code> 属性，又是支持每个缓存允许自定义配置。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// RedisCacheManager.java</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 默认的全局配置</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> RedisCacheConfiguration defaultCacheConfig;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 每个缓存的自定义配置</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Map&lt;String, RedisCacheConfiguration&gt; initialCacheConfiguration;</span><br></pre></td></tr></table></figure><p>所以，我们可以参考 <a href="https://blog.csdn.net/zyt807/article/details/82428615" target="_blank" rel="noopener">《SpringBoot 2.0 的 @Cacheable(Redis) 缓存失效时间解决方案》</a> 文章，自定义一个 CacheManager Bean 对象。当然，更加系统的解决方式，是按照这个文章的思路，实现一个新的支持使用配置文件自定义每个缓存配置的 <a href="https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/RedisCacheConfiguration.java" target="_blank" rel="noopener">RedisCacheConfiguration</a> 自动化配置类。</p><p>当然，还有一个解决方案，就是使用 Redisson 提供的缓存管理器。具体可以看看 <a href="https://github.com/redisson/redisson/wiki/14.-第三方框架整合#142-spring-cache整合" target="_blank" rel="noopener">《Redisson 文档 —— Spring Cache整合》</a> 。</p><h1 id="666-彩蛋"><a href="#666-彩蛋" class="headerlink" title="666. 彩蛋"></a>666. 彩蛋</h1><p>快乐的时光，总是这么短暂。我们已经成功完成了对 Spring Boot 如何集成 Spring Cache 的入门。下面还是进入我们的日常彩蛋环节。</p><p>Guava 也提供了本地缓存的功能，但是 <code>spring-boot-starter-cache</code> 2.X 的版本，并未提供对它的内置支持。原因我们可以在 <a href="https://stackoverflow.com/questions/44175085/why-did-spring-framework-deprecate-the-use-of-guava-cache" target="_blank" rel="noopener">Why did Spring framework deprecate the use of Guava cache?</a> 看到，Spring 5.X 版本，从 Guava 替换成了 <a href="https://github.com/ben-manes/caffeine" target="_blank" rel="noopener">Caffeine</a> 。如果胖友使用的是 Spring Boot 1.X 的版本，倒是可以看看 <a href="https://blog.csdn.net/u011726984/article/details/79013055" target="_blank" rel="noopener">《Spring Boot + Guava Cache + @EnableCaching》</a> 文章。</p><p>在推荐两篇大厂在缓存方面的实践：</p><ul><li>美团技术团队 <a href="https://tech.meituan.com/2017/03/17/cache-about.html" target="_blank" rel="noopener">《缓存那些事》</a></li><li>有赞技术团队 <a href="http://www.iocoder.cn/Fight/A-nifty-multilevel-cache-implementation-scheme-how-nifty-is-it/self" target="_blank" rel="noopener">《有赞透明多级缓存解决方案（TMC）》</a></li></ul><p>除了 Spring Cache 缓存框架之外，我们也可以考虑如下的解决方案：</p><ul><li><a href="https://github.com/alibaba/jetcache" target="_blank" rel="noopener">JetCache</a></li><li><a href="https://gitee.com/ld/J2Cache" target="_blank" rel="noopener">J2Cache</a></li></ul><p>推荐阅读：</p><ul><li><a href="http://www.iocoder.cn/Performance-Testing/Redis-benchmark/?self" target="_blank" rel="noopener">《性能测试 —— Redis 基准测试》</a></li><li><a href="http://www.iocoder.cn/Spring-Boot/Redis/?self" target="_blank" rel="noopener">《芋道 Spring Boot Redis 入门》</a></li></ul></div><div class="article-footer"><blockquote class="mt-2x"><ul class="post-copyright list-unstyled"><li class="post-copyright-link hidden-xs"><strong>本文链接：</strong> <a href="https://calm_java.gitee.io/blog/2020/01/09/labs-springboot2-cache/" title="Spring Boot 缓存 Cache 入门" target="_blank" rel="external">https://calm_java.gitee.io/blog/2020/01/09/labs-springboot2-cache/</a></li><li class="post-copyright-license"><strong>版权声明： </strong>本博客所有文章除特别声明外，均采用 <a href="http://creativecommons.org/licenses/by/4.0/deed.zh" target="_blank" rel="external">CC BY 4.0 CN协议</a> 许可协议。转载请注明出处！</li></ul></blockquote><div class="panel panel-default panel-badger"><div class="panel-body"><figure class="media"><div class="media-left"><a href="https://github.com/yuyong725" target="_blank" class="img-burn thumb-sm visible-lg"><img src="/blog/images/avatar.jpg" class="img-rounded w-full" alt=""></a></div><div class="media-body"><h3 class="media-heading"><a href="https://github.com/yuyong725" target="_blank"><span class="text-dark">卡姆</span><small class="ml-1x">a Java coder need calm</small></a></h3><div>享受编程</div></div></figure></div></div></div></article><section id="comments"></section></div><nav class="bar bar-footer clearfix" data-stick-bottom><div class="bar-inner"><ul class="pager pull-left"><li class="prev"><a href="/blog/2020/01/09/labs-springboot2-redis/" title="Spring Boot Redis 入门"><i class="icon icon-angle-left" aria-hidden="true"></i><span>&nbsp;&nbsp;上一篇</span></a></li><li class="next"><a href="/blog/2020/01/08/labs-springboot2-jpa/" title="Spring Boot JPA 入门"><span>下一篇&nbsp;&nbsp;</span><i class="icon icon-angle-right" aria-hidden="true"></i></a></li><li class="toggle-toc"><a class="toggle-btn" data-toggle="collapse" href="#collapseToc" aria-expanded="true" title="文章目录" role="button"><span>[&nbsp;</span><span>文章目录</span> <i class="text-collapsed icon icon-anchor"></i> <i class="text-in icon icon-close"></i> <span>]</span></a></li></ul><button type="button" class="btn btn-fancy btn-donate pop-onhover bg-gradient-warning" data-toggle="modal" data-target="#donateModal"><span>赏</span></button><div class="bar-right"><div class="share-component" data-sites="wechat" data-mobile-sites="wechat"></div></div></div></nav><div class="modal modal-center modal-small modal-xs-full fade" id="donateModal" tabindex="-1" role="dialog"><div class="modal-dialog" role="document"><div class="modal-content donate"><button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button><div class="modal-body"><div class="donate-box"><div class="donate-head"><p>感谢您的支持，我会继续努力的!</p></div><div class="tab-content"><div role="tabpanel" class="tab-pane fade active in" id="alipay"><div class="donate-payimg"><img src="/blog/images/donate/alipayimg.jpeg" alt="扫码支持" title="扫一扫"></div><p class="text-muted mv">扫码打赏，你说多少就多少</p><p class="text-grey">打开支付宝扫一扫，即可进行扫码打赏哦</p></div><div role="tabpanel" class="tab-pane fade" id="wechatpay"><div class="donate-payimg"><img src="/blog/images/donate/wechatpayimg.jpeg" alt="扫码支持" title="扫一扫"></div><p class="text-muted mv">扫码打赏，你说多少就多少</p><p class="text-grey">打开微信扫一扫，即可进行扫码打赏哦</p></div></div><div class="donate-footer"><ul class="nav nav-tabs nav-justified" role="tablist"><li role="presentation" class="active"><a href="#alipay" id="alipay-tab" role="tab" data-toggle="tab" aria-controls="alipay" aria-expanded="true"><i class="icon icon-alipay"></i> 支付宝</a></li><li role="presentation"><a href="#wechatpay" role="tab" id="wechatpay-tab" data-toggle="tab" aria-controls="wechatpay" aria-expanded="false"><i class="icon icon-wepay"></i> 微信支付</a></li></ul></div></div></div></div></div></div></main><footer class="footer" itemscope itemtype="http://schema.org/WPFooter"></footer><script src="//cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js"></script><script>window.jQuery||document.write('<script src="js/jquery.min.js"><\/script>')</script><script src="/blog/js/plugin.min.js"></script><script src="/blog/js/application.js"></script><script>!function(T){var N={TRANSLATION:{POSTS:"文章",PAGES:"页面",CATEGORIES:"分类",TAGS:"标签",UNTITLED:"(未命名)"},ROOT_URL:"/blog/",CONTENT_URL:"/blog/content.json"};T.INSIGHT_CONFIG=N}(window)</script><script src="/blog/js/insight.js"></script><script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script><script src="//cdn.jsdelivr.net/npm/gitalk@1.4.0/dist/gitalk.min.js"></script><script src="//cdn.jsdelivr.net/npm/blueimp-md5@2.10.0/js/md5.min.js"></script><script type="text/javascript">var gitalk=new Gitalk({clientID:"adf0c8bfdd8796029917",clientSecret:"a88c2e2e83e2b180f62ad927dcb347871dfe9f4b",repo:"myblog-comment",owner:"yuyong725",admin:["yuyong725"],id:md5(location.pathname),distractionFreeMode:!0});gitalk.render("comments")</script></body></html><!-- rebuild by neat -->